Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kie-issues#1298: Decision Services & multiple DRDs: make it possible o add external Decisions to a Decision Service #2508

Open
wants to merge 39 commits into
base: main
Choose a base branch
from

Conversation

danielzhe
Copy link
Contributor

@danielzhe
Copy link
Contributor Author

More testes for you, @ljmotta 😄

import { useCallback, useMemo, useRef, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes are made automatically by the Prettier

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Prettier doesn't change the imports... Maybe it's your VS Code "Organize imports"?

Copy link
Contributor

@tiagobento tiagobento left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @danielzhe! Made some change requests inline, please consider them.

Comment on lines 51 to 59
}: {
definitions: Normalized<DMN15__tDefinitions>;
decisionId: string;
drgElement: DrgElement;
decisionServiceId: string;
drdIndex: number;
snapGrid: SnapGrid;
decisionShape: Normalized<DMNDI15__DMNShape>;
elementId: string;
}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elementId --> decisionId

I understand drgElement doesn't necessarily is inside definitions, so please make this action dependent on external models too. There's no way to know whether or not drgElement and elementId are aligned...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous code we was getting the ID, in Diagram.tsx using the following code:

decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // We can assume that all selected nodes are Decisions because the contaiment was validated above.

So I think it is safe to assume that selectedNodes[i].data.dmnObject is always a Decision which is the DrgElement that we looking for. Maybe I should change the type of DrgElement I defined here
to:

export type DecisionDrgElement = Normalized<{ __$$element: "decision" } & DMN15__tDecision>

About the ID, I can't see any case where the ID of the selected node will be different than the DRG, except for the imported nodes the ID in the DRG is not full, it is only the local ID (the Guid), but from selectedNodes[I].["@_id"] is the real ID used in the current diagram (namespace + id).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, we don't have DRG element always in the definitions: the external nodes do not have DRG element, only DMNDI. The DRG we have in selectedNodes[i].data.dmnObject is a loaded DRG from the external model, that's why its ID is different to the internal one, the one in href.
See below:

  <decisionService name="New Decision Service" id="_A0908B2B-DBE7-41C7-B055-0C0DAD17CE54">
    <variable name="New Decision Service" id="_145CD7A7-B14D-4D63-B9D3-85601FC915B1" />
    <outputDecision href="https://kie.apache.org/dmn/_D19B0015-2CBD-4BA8-84A9-5F554D84A9E1#_05621ED4-9236-47F1-B93A-164A4527B136" />
    <encapsulatedDecision href="https://kie.apache.org/dmn/_D19B0015-2CBD-4BA8-84A9-5F554D84A9E1#_1991FB34-1253-4A54-AD3D-89697938DDFA" />
    <encapsulatedDecision href="#_8E89DDF6-7ABC-4FBF-A1B6-B68BC32A52AE" />
  </decisionService>
  <decision name="New Decision" id="_8E89DDF6-7ABC-4FBF-A1B6-B68BC32A52AE">
    <variable name="New Decision" id="_BFB8A43B-5ADA-47D8-815E-8A0505814AA1" />
  </decision>

Notice only the 8E89DDF6-7ABC-4FBF-A1B6-B68BC32A52AE is present in the file.
The other nodes are not, only as a dmndi.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need the ID, but we need to know that this ID represents a Decision. The only way to know that is having the externalModelsByNamespace passed as parameter to the "addDecisionToDecisionService" mutation, so we can make that validation. You're mentioning the arguments you're passing when calling this mutation, but the mutation itself, after your changes, can be called with

{ 
  elementId: "bar"
  drgElement: { 
    "@_id": "foo" 
    ...
  }
}

which has the potential of being wrong. What I'm saying is that we rollback to the way it was before, and we include the externalModelsByNamespace parameters so we can look if that href (nmspc#id) represents a Decision on the model with namespace nmspc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think got your point! I guarantee in the Diagram.tsx that the ID represents a decision, but I am not doing the same inside the mutation, in other words, the mutation is not "safe" anymore, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can guarantee that you're calling it correctly now, but you can't guarantee that this will be true forever :P

Comment on lines 248 to 252
<div
className={"kie-dmn-editor--palette-nodes-popover"}
style={{ maxHeight }}
data-testid={"kie-tools--dmn-editor--external-nodes-container"}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "DRG nodes" panel doesn't have a data-testid, why don't we use the same strategy we're using for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests for "DRG Nodes" panel? I only found for drag nodes from the palette not from the "DRG Nodes" panel. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiagobento actually we are about to add data-testid also for the DRG Nodes panel, see the PR #2462

One thing we could unify is the data-testid suffix. @danielzhe is using -container while me is using -popover, what do you prefer? I chose -popover due to related className.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good then! Thanks for the insights. I have no preference for the suffix, just please coordinate that it is consistent. @jomarko @danielzhe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change it to -popover since it is related to the className and makes more sense! Thank you, @jomarko

Comment on lines 1018 to 1019
decisionShape: selectedNodes[i].data.shape,
elementId: selectedNodes[i].id, // The "real" id is here, which can be the local id or an imported node id (uri + external element id).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my comment about the mutation itself...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this to externalNodes? I mean, this is the first test we have on that "domain".

await editor.changeTab({ tab: TabName.EDITOR });
await palette.dragNewNode({ type: NodeType.DECISION_SERVICE, targetPosition: { x: 400, y: 200 } });

await palette.getExternalNodesButton().click();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is leaking internal details... I would expect something like palette.openExternalNodesPanel().

test("add to a Decision Service", async ({ editor, page, palette, diagram, nodes, includedModels }) => {
await editor.changeTab({ tab: TabName.INCLUDED_MODELS });

await includedModels.getIncludeModelButton().click();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is leaking internal details... I would expect something like includedModels.addNew(). Not sure how this is being done in other parts of our test suite, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did as you suggested at first, but then I found in other tests that we were using this way, getting the button and then calling the click(), like here, for example, and changed to this "clicky" approach. I will change it back. 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I like it better when the actual screen elements are not exposed, to be honest, since that's how we're doing for the other parts of our tests.

});

// We click on the button again to close the external nodes panel.
await palette.getExternalNodesButton().click();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same? Maybe instead of openExternalNodesPanel you could call it toggleExternalNodesPanel

Comment on lines 70 to 72
// We move it to make it sure that the nodes are added inside the Decision Services
// and are really attached to it, not only "visually over it".
await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 120, y: 120 } });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great comment.


await expect(diagram.get()).toHaveScreenshot("add-included-node-inside-decision-service.png");
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add more tests to the basic operations, like changing from one DS to another. Removing an external Decision from a DS, things like that.

Comment on lines +144 to +150
// Decision Services only have some draggable areas near the borders.
// If you drag it to the center, you'll drag the divide line.
// Also, neither the entire upper area nor the entire downer area is draggable.
await this.get({ name: args.name }).dragTo(this.diagram.get(), {
targetPosition: args.targetPosition,
sourcePosition: { x: 20, y: 20 },
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great comment also.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we have one more problem, that node.move() can be invoked also for a decision service node, that is already renamed and is completely different from DefaultNodeName.DECISION_SERVICE

I think we should also document that the decision service move will work only for 'default name' decision service nodes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point @jomarko. @danielzhe I think we can update this to have a flag "grabAtBorders" that we use as true when moving Decision Services around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a recent change where the name must now contain DefaultNodeName.DECISION_SERVICE, but not exactly it. But I agree, it isn't good to rely on the name. What we can do? A separate "move" for Decision Service or add a doc in the code?

We also can just change the move to always get the node from a corner not from the middle. Then it will work for every node. Can add an optional parameter where the user can add the sourcePosition also. I think this is the best option. What do you guys think? @jomarko @tiagobento

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  public async move(args: { grabAtBorder?: boolean; targetPosition: { x: number; y: number } }) {
    if (args.grabAtBorder ?? false) {
      // Decision Services only have some draggable areas near the borders.
      // If you drag it to the center, you'll drag the divide line.
      // Also, neither the entire upper area nor the entire downer area is draggable.
      await this.get({ name: args.name }).dragTo(this.diagram.get(), {
        targetPosition: args.targetPosition,
        sourcePosition: { x: 20, y: 20 },
      });
    } else {
      await this.get({ name: args.name }).dragTo(this.diagram.get(), {
        targetPosition: args.targetPosition,
      });
    }
  }

@jomarko
Copy link
Contributor

jomarko commented Aug 5, 2024

I will review this tomorrow.

Copy link
Contributor

@jomarko jomarko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments inline, however manual check passed and the feature works well!

@@ -159,6 +159,7 @@ export function ExternalNodesPanel() {
externalDrgElementId: drgElement["@_id"]!,
})
}
data-testid={`kie-tools--dmn-editor--external-node-${_import["@_name"]}-${drgElement["@_name"]}`}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ${_import["@_name"]} always non empty? I mean containing some non white characters?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it can be an empty string. Great catch.

Comment on lines +144 to +150
// Decision Services only have some draggable areas near the borders.
// If you drag it to the center, you'll drag the divide line.
// Also, neither the entire upper area nor the entire downer area is draggable.
await this.get({ name: args.name }).dragTo(this.diagram.get(), {
targetPosition: args.targetPosition,
sourcePosition: { x: 20, y: 20 },
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we have one more problem, that node.move() can be invoked also for a decision service node, that is already renamed and is completely different from DefaultNodeName.DECISION_SERVICE

I think we should also document that the decision service move will work only for 'default name' decision service nodes.

Comment on lines 43 to 56
public async dragExternalNode(args: {
includedModelName: string;
nodeName: string;
targetPosition: { x: number; y: number };
}) {
await this.page
.getByTestId("kie-tools--dmn-editor--external-nodes-container")
.getByTestId(`kie-tools--dmn-editor--external-node-${args.includedModelName}-${args.nodeName}`)
.dragTo(this.diagram.get(), { targetPosition: args.targetPosition });
}

public async toggleExternalNodesPanel() {
await this.page.getByRole("button", { name: "External nodes" }).click();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my PR #2462, I added similar as separate fixture drgNodes.ts. Should I also add add it into palette.ts instead of a separate fixture file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved to palette because we understand the "palette" as all those buttons on the left of the screen. So I think the toggle should be done in the palette.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is not so clear , if the MY_MODEL_Diff is really out of the decision service node. Could we maybe move it a little bit more to make it clear? Or is this something like corner case test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, this screenshot is kind of ambiguous...

Comment on lines +33 to +34
await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why both fillModelToInclude and selectModel are needed please?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One "types" the string, and the other picks it from the list.

@@ -992,8 +992,9 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
for (let i = 0; i < selectedNodes.length; i++) {
deleteDecisionFromDecisionService({
definitions: state.dmn.model.definitions,
decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // We can assume that all selected nodes are Decisions because the contaiment was validated above.
decisionId: selectedNodes[i].id, // We can assume that all selected nodes are Decisions because the contaiment was validated above.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the full id. The ID of the selectedNodes[i].data.dmnObject!["@_id"]! does not contain the href to the external model, only the "internal" id, so inside the mutation we would have to look everywhere looking for where is the Decision.

Copy link
Contributor

@tiagobento tiagobento Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • "Full ID" = href
  • href = namespace#xsd:ID
  • xsd:ID = typically the "@_id" attribute of objects
  • namespace = definitions["@_namespace"]

const decision = definitions.drgElement?.find((s) => s["@_id"] === decisionId);
if (decision?.__$$element !== "decision") {
throw new Error(`DMN MUTATION: DRG Element with id '${decisionId}' is either not a Decision or doesn't exist.`);
const href = parseXmlHref(decisionId);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So decisionId can be in any format here:
#id
uri#id
id

But, if it is an external node, we only be able to find it if the full id is passed: uri#id

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think we better rename decisionId to decisionHref.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and the id of a node is an href.... please see apache/incubator-kie-issues#983.... :x

@danielzhe
Copy link
Contributor Author

@tiagobento @jomarko Changes applied! Ready for you to continue.

Copy link
Contributor

@tiagobento tiagobento left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @danielzhe! This is looking great. Few last comments from my side.

packages/dmn-editor/src/diagram/Diagram.tsx Outdated Show resolved Hide resolved
Comment on lines 94 to 97
decisionService.encapsulatedDecision.push({ "@_href": `${hrefString}` });
} else if (section === "output") {
decisionService.outputDecision ??= [];
decisionService.outputDecision.push({ "@_href": `#${decisionId}` });
decisionService.outputDecision.push({ "@_href": `${hrefString}` });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

Comment on lines 69 to 73
const xmlHref = buildXmlHref({ namespace: href.namespace, id: href.id });
decisionService.outputDecision = (decisionService.outputDecision ?? []).filter((s) => s["@_href"] !== `${xmlHref}`);
decisionService.encapsulatedDecision = (decisionService.encapsulatedDecision ?? []).filter(
(s) => s["@_href"] !== `#${decisionId}`
(s) => s["@_href"] !== `${xmlHref}`
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

import { useCallback, useMemo, useRef, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Prettier doesn't change the imports... Maybe it's your VS Code "Organize imports"?

Comment on lines 58 to 59
const externalDrgs = (externalModel.model as Normalized<DmnLatestModel>).definitions.drgElement;
const decision = externalDrgs?.find((drgElement) => drgElement["@_id"] === href.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal preference, but I always think we should avoid intermediate variables to avoid giving it a name. It's one more thing to hold onto my head when reading code...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good topic.
I like to split in variables because I find it easier to "think" step-by-step and have less complexity per line.

Also, in this case, I'm giving "meaning" to what (externalModel.model as Normalized<DmnLatestModel>).definitions.drgElement: it is the "external drgs". So I don't have to care where it comes from in line 59 or what it is. It is the "external drgs that come from somewhere". Ok, "somewhere" is just a line above, but in my mind, looking at line 59, it is a "problem solved" that I don't have to care about anymore.

But the main point is that if someone new looks at the code it can easily get what (externalModel.model as Normalized<DmnLatestModel>).definitions.drgElement means.

Comment on lines 48 to 49
const externalDrgs = (externalModel.model as Normalized<DmnLatestModel>).definitions.drgElement;
const decision = externalDrgs?.find((drgElement) => drgElement["@_id"] === href.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also same as above.

@danielzhe
Copy link
Contributor Author

@tiagobento Changes applied!

@jomarko
Copy link
Contributor

jomarko commented Sep 9, 2024

Sorry, I get lost in the conversation, since my approval it seems more code landed in. Should I do a re-reiew?

Copy link
Contributor

@ljmotta ljmotta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made some comments inline. One tip regarding objects. When the object property and value has the same name you could short it just the property name:

drgElementsByNamespace: drgElementsByNamespace,
externalModelsByNamespace: externalModelsByNamespace,
drgElementsByNamespace,
externalModelsByNamespace,

Also, all test methods are with the page in their signatures, but none use it.

Example:
async ({ editor, page, palette, diagram, nodes, includedModels })

@@ -54,8 +77,11 @@ export function addDecisionToDecisionService({
}

const diagram = addOrGetDrd({ definitions, drdIndex });
const hrefString = buildXmlHref({ namespace: href.namespace, id: href.id });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decisionHref isn't the same as the hrefString?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. The decisionHref can be something like uri#id, id, or even #id.

The buildXmlHref makes sure that we have uri#id or #id

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A href will always have the the # on its structure. If it's possible to have a href variable without it (an id value), the variable is wrongly named. Now, looking at the source of this value, it comes from the computeDiagramData.ts file which creates the selectedNodesById map. The map id is a value made with the buildXmlHref, so all ids are href, meaning the decisionHref is correctly named.

I think this is a problem in our codebase, where we mix the id and the href, and at some point we need to address this. FYI @tiagobento

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielzhe @tiagobento how are we going to proceed here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielzhe Please use decisionHref where hrefString is currently being used, since they are exactly the same string.

decisionService.outputDecision = (decisionService.outputDecision ?? []).filter(
(s) => s["@_href"] !== `#${decisionId}`
);
const xmlHref = buildXmlHref({ namespace: href.namespace, id: href.id });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same here. xmlHref will not have the same value as the decisionHref?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer as above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xmlHref is exactly the same as href... you're even building it with the same values. No need to have this xmlHref variable...

Comment on lines +60 to +64
if (decision?.__$$element !== "decision") {
throw new Error(
`DMN MUTATION: DRG Element with id '${href.id}' is either not a Decision or doesn't exist in the external model '${href.namespace}'`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message is targeting two different cases. Not having an element and the element not being a Decision are two distinguish scenarios. I think this should be break into two errors. If decision === undefined the element doesn't exist message. And the decision.__$$element !== "decision" the error telling isn't a Decision. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following the existing error message patterns used in this mutation and in deleteDecisionFromDecisionService. In my opinion, we will only be adding more code to do something that we can do in fewer lines.

Comment on lines +50 to +54
if (decision?.__$$element !== "decision") {
throw new Error(
`DMN MUTATION: DRG Element with id '${href.id}' is either not a Decision or doesn't exist in the external model '${href.namespace}'`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same with this error.

Comment on lines 88 to 124
for (const hrefString of hrefsToDecisionsInsideDecisionService) {
const href = parseXmlHref(hrefString);
const externalModel = externalModelsByNamespace?.[href.namespace ?? ""];
if (externalModel) {
const externalDecision = (externalModel?.model as Normalized<DmnLatestModel>).definitions.drgElement?.find(
(drgElement) => drgElement["@_id"] === href.id
) as Normalized<DMN15__tDecision>;

if (externalDecision) {
(externalDecision.informationRequirement ?? []).flatMap((ir) => {
if (ir.requiredDecision) {
const externalHref = parseXmlHref(ir.requiredDecision["@_href"]);
// If the requiredDecision has namespace, it means that it is pointing to a node in a 3rd model,
// not this one (the local model) neither the model in the `href.namespace`.
if (externalHref.namespace) {
requirements.set(`${ir.requiredDecision["@_href"]}`, "decisionIr");
} else {
requirements.set(`${href.namespace}${ir.requiredDecision["@_href"]}`, "decisionIr");
}
} else if (ir.requiredInput) {
// If the requiredInput has namespace, it means that it is pointing to a node in a 3rd model,
// not this one (the local model) neither the model in the `href.namespace`.
const externalHref = parseXmlHref(ir.requiredInput["@_href"]);
if (externalHref.namespace) {
requirements.set(`${ir.requiredInput["@_href"]}`, "inputDataIr");
} else {
requirements.set(`${href.namespace}${ir.requiredInput["@_href"]}`, "inputDataIr");
}
} else {
throw new Error(
`DMN MUTATION: Invalid information requirement referenced by external DecisionService: '${externalDecision["@_id"]}'`
);
}
});
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding just a suggestion here. The usage of guard clauses to reduce nesting:

if (externalModel === undefined) {
    continue;
}
const externalDecision = (externalModel.model as Normalized<DmnLatestModel>).definitions.drgElement?.find((drgElement) => drgElement["@_id"] === href.id) as Normalized<DMN15__tDecision>;

if (externalDecision === undefined) {
    continue;
}

...

Also, why did you used the flatMap instead of forEach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code with flatMap is a copy-paste of the same code a few lines above, but it uses the drgElement and has a simpler logic. Do you want me to change both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point (the nesting)!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please. It doesn't make sense to use flatMap if we're not retuning the value.

stories: async ({ baseURL, page }, use) => {
await use(new Stories(page, baseURL));
},
includedModels: async ({ baseURL, page }, use) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused baseUrl

@danielzhe
Copy link
Contributor Author

I've made some comments inline. One tip regarding objects. When the object property and value has the same name you could short it just the property name:

I'm aware of this. If I didn't do it probably I just overlooked it or it was something that was renamed. 😄 @ljmotta

I'm checking the other things you reported. Thank you.

@danielzhe
Copy link
Contributor Author

Changes, applied, @ljmotta

@@ -471,6 +472,7 @@ function IncludedModelCard({
definitions: state.dmn.model.definitions,
__readonly_index: index,
__readonly_externalModelTypesByNamespace: externalModelTypesByNamespace,
externalModelsByNamespace,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__readonly_externalModelsByNamespace

@@ -876,6 +879,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
__readonly_externalModelTypesByNamespace: state
.computed(state)
.getExternalModelTypesByNamespace(externalModelsByNamespace),
externalModelsByNamespace,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__readonly_externalModelsByNamespace

packages/dmn-editor/src/diagram/Diagram.tsx Outdated Show resolved Hide resolved
decisionService.outputDecision = (decisionService.outputDecision ?? []).filter(
(s) => s["@_href"] !== `#${decisionId}`
);
const xmlHref = buildXmlHref({ namespace: href.namespace, id: href.id });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xmlHref is exactly the same as href... you're even building it with the same values. No need to have this xmlHref variable...

packages/dmn-editor-envelope/src/DmnEditorRoot.tsx Outdated Show resolved Hide resolved
Comment on lines 641 to 647
loadDependentModels(
includedModel,
externalModelsIndex,
resourcesByNamespace,
loadedDmnsByPathRelativeToTheWorkspaceRoot,
thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This useCallback depends on itself.. This is not very good I'd say. Not even sure how ESLint didn't complain about this, as you're inhenritenly depending on loadDependentModels from a previous tick (I.e., outdated getIncludedNamespacesFromModel).

Copy link
Contributor

@ljmotta ljmotta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great changes, the rename helped a lot. I've made some comments inline regarding error messages and minor things.

@@ -54,8 +77,11 @@ export function addDecisionToDecisionService({
}

const diagram = addOrGetDrd({ definitions, drdIndex });
const hrefString = buildXmlHref({ namespace: href.namespace, id: href.id });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielzhe @tiagobento how are we going to proceed here?

@@ -20,21 +20,45 @@
import { DMN15__tDefinitions } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { repopulateInputDataAndDecisionsOnDecisionService } from "./repopulateInputDataAndDecisionsOnDecisionService";
import { Normalized } from "../normalization/normalize";
import { buildXmlHref, parseXmlHref } from "../xml/xmlHrefs";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused import

packages/dmn-editor/src/mutations/deleteImport.ts Outdated Show resolved Hide resolved

const nodeTypeTooltipDescription = useMemo(() => {
if (dmnObject === undefined) {
throw new Error("nodeTypeDescription can't be defined without a DMN object");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nodeTypeDescription and DMN Object doesn't make much sense in the error message. The dmnObject is a drgElement, I'm not sure about the context behind this name, maybe @tiagobento could help us here. "Can't define the tooltip description without a DRG element" WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the name dmnObject is definitely sub-optimal. That's on me, though. We just need to be careful to not crash the editor over an error like this. Let's define to the "Unknown" string and remove both throws please.

@danielzhe

}
const nodeType = getNodeTypeFromDmnObject(dmnObject);
if (nodeType === undefined) {
throw new Error("Can't determine nodeTypeDescription with undefined node type");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same here. "Can't define the tooltip description without the node type" WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. @danielzhe

@ljmotta
Copy link
Contributor

ljmotta commented Sep 19, 2024

I found some interesting cases doing some manual checkings:

  1. We have here three DRDs, in DRD3 we can observe that the ds2 is in collapsed form, and d3 isn't available to pick from the node palette, as the node was depict.

image

image

image

  1. Dragging a node near the collapse DS will trigger the invalid operation in the DS.

image

  1. It's possible to hide external Decisions that are part of a Decision Service. Adding them back to the DRD will not remove them from the DS, resulting some weird behaviors
Recording.2024-09-18.212657.mp4
  1. A DRD that has Decision nodes that are part of a collapsed Decision Service can't be moved:
Recording.2024-09-18.212950.mp4

@tiagobento
Copy link
Contributor

@ljmotta Can you please discriminate between what's already on main and what's been introduced by this PR? Some of those bugs (E.g., Dragging a node near the collapse DS will trigger the invalid operation in the DS.) you reported seem not to be related to the changes this PR introduces.

@tiagobento
Copy link
Contributor

@danielzhe I would double-check those too, just to be sure.

@ljmotta
Copy link
Contributor

ljmotta commented Sep 19, 2024

@tiagobento Both, 1 and 2 are present on main. I could reproduce using the daily-dev.

@danielzhe
Copy link
Contributor Author

Recording.2024-09-26.175024.mp4

@danielzhe
Copy link
Contributor Author

moving.tests.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decision Services & multiple DRDs: make it possible to add external Decisions to a Decision Service
4 participants